/*
 * Copyright (c) 2012 Sean Connelly
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

//
// commonly used functions across entire js base
//

function common_isArray(obj)
{
	return Object.prototype.toString.call(obj) === '[object Array]';
}

function common_isObject(obj)
{
	return typeof obj === 'object' && obj !== null && !common_isArray(obj);
}

function common_isEmptyObject(obj)
{
	for (var k in obj)
		return false;
	return true;
}

function common_stringRepeat(str, cnt)
{
	return (new Array(cnt + 1)).join(str);
}

function common_objectsMatch(obj1, obj2)
{
	var conv = function(v) {
			switch (typeof v)
			{
			case 'boolean':
			case 'number':
			case 'string':
			case 'function':
				return (typeof v) + '|1' + ('' + v).replace(/&/g, '&a').replace(/\|/g, '&p') + '|2';
			case 'object':
				if (v === 'null')
					return '|3';
				else if (common_isArray(v))
				{
					var out = 'array|4';
					for (var i = 0; i < v.length; i++)
						out += conv(v[i]) + '|5';
					return out + '|6';
				}
				var keys = [];
				for (var k in v)
					keys.push(k);
				keys.sort();
				var out = 'object|7[';
				for (var i = 0; i < keys.length; i++)
					out += keys[i] + '|8' + conv(v[keys[i]]) + '|9';
				return out + '|a';
			default:
				throw 'unknown type in common.objectsMatch ' + typeof v;
			}
		};
	return conv(obj1) === conv(obj2);
}

function common_niceJson(obj)
{
	var tc = '\t';
	var out;
	var objout = function(obj, tab) {
			var str = '';
			for (var k in obj)
			{
				var val = out(obj[k], false, tab);
				if (val !== false)
					str += (str === '' ? '' : ',\n') + common_stringRepeat(tc, tab) + k + ': ' + val;
			}
			return str;
		};
	out = function(obj, flatobj, tab) {
			var str = '';
			switch (typeof obj)
			{
			case 'boolean':
				return obj ? 'true' : 'false';
			case 'number':
				return '' + obj;
			case 'string':
				return '"' + obj
					.replace(/\r/g, '\\r')
					.replace(/\n/g, '\\n')
					.replace(/\t/g, '\\t')
					.replace(/\\/g, '\\\\')
					.replace(/"/g, '\\"') + '"';
			case 'object':
				if (obj === null)
					return 'null';
				if (common_isArray(obj))
				{
					if (obj.length <= 0)
						return '[]';
					var allObj = true;
					for (var i = 0; i < obj.length && allObj; i++)
						allObj = common_isObject(obj[i]);
					if (allObj) // array full of objects
					{
						for (var i = 0; i < obj.length; i++)
						{
							if (str !== '')
								str += '\n' + common_stringRepeat(tc, tab + 1) + '},{\n';
							str += objout(obj[i], tab + 2);
						}
						return '[{\n' + str + '\n' + common_stringRepeat(tc, tab + 1) + '}]';
					}
					// otherwise, regular flat array
					for (var i = 0; i < obj.length; i++)
					{
						var val = out(obj[i], true, 0);
						if (val !== false)
							str += (str === '' ? '' : ', ') + val;
					}
					return '[' + str + ']';
				}
				// else, object
				if (common_isEmptyObject(obj))
					return '{}';
				if (flatobj)
				{
					for (var k in obj)
					{
						var val = out(obj[k], true, 0);
						if (val !== false)
							str += (str === '' ? '' : ', ') + k + ': ' + val;
					}
					return '{ ' + str + ' }';
				}
				// else, tabbed object
				return '{\n' + objout(obj, tab + 2) + '\n' + common_stringRepeat(tc, tab + 1) + '}';
			default:
				return false;
			}
		};
	return out(obj, false, 0);
}

function common_deepCopy(obj)
{
	switch (typeof obj)
	{
	case 'number':
	case 'string':
	case 'boolean':
	case 'function':
		return obj;
	case 'object':
		if (obj === null)
			return null;
		var ret;
		if (common_isArray(obj))
			ret = [];
		else
			ret = {};
		for (var k in obj)
			ret[k] = common_deepCopy(obj[k]);
		return ret;
	default:
		throw 'unknown type in deepCopy';
	}
}

function common_replaceAll(haystack, needle, repwith)
{
	return haystack.split(needle).join(repwith);
}

function common_enumArray() // enumArray('One', 'Two') ==> { One: 0, Two: 1 }
{
	var obj = {};
	for (var i = 0; i < arguments.length; i++)
		obj[arguments[i]] = i;
	return obj;
}

function common_iscWhitespace(ch) // MYTODO: include unicode chars
{
	// this one is constant and doesn't change
	return ch !== false && (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n');
}

function common_isWhite(ch) // MYTODO: include unicode chars
{
	// this one tracks Unicode
	return ch !== false && (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n');
}

function common_isDigit(ch)
{
	return ch !== false && (ch.charCodeAt(0) >= '0'.charCodeAt(0) &&
		ch.charCodeAt(0) <= '9'.charCodeAt(0));
}

function common_isHex(ch)
{
	ch = ch === false ? false : ch.toLowerCase();
	return ch !== false && (ch == 'a' || ch == 'b' || ch == 'c' || ch == 'd' || ch == 'e' ||
		ch == 'f' || common_isDigit(ch));
}

function common_isOct(ch)
{
	return ch !== false && (ch.charCodeAt(0) >= '0'.charCodeAt(0) &&
		ch.charCodeAt(0) <= '7'.charCodeAt(0));
}

function common_isBin(ch)
{
	return ch !== false && (ch == '0' || ch == '1');
}

function common_isAlpha(ch)
{
	ch = ch === false ? false : ch.toLowerCase();
	return ch !== false && (ch.charCodeAt(0) >= 'a'.charCodeAt(0) &&
		ch.charCodeAt(0) <= 'z'.charCodeAt(0));
}

function common_isLetter(ch) // MYTODO: add unicode alpha testing
{
	return common_isAlpha(ch);
}

function common_isIdentStart(ch)
{
	return ch !== false && (ch == '_' || ch == '$' || common_isAlpha(ch));
}

function common_isIdentMiddle(ch)
{
	return common_isIdentStart(ch) || common_isDigit(ch);
}

function common_isWord(ch)
{
	return common_isLetter(ch) || common_isIdentMiddle(ch);
}

function common_upperFirst(txt) // make first character uppercase
{
	if (typeof txt != 'string' || txt.length == 0)
		return txt;
	return txt.charAt(0).toUpperCase() + txt.slice(1);
}

exports.isArray = common_isArray;
exports.isObject = common_isObject;
exports.isEmptyObject = common_isEmptyObject;
exports.stringRepeat = common_stringRepeat;
exports.objectsMatch = common_objectsMatch;
exports.niceJson = common_niceJson;
exports.deepCopy = common_deepCopy;
exports.replaceAll = common_replaceAll;
exports.enumArray = common_enumArray;
exports.iscWhitespace = common_iscWhitespace;
exports.isWhite = common_isWhite;
exports.isDigit = common_isDigit;
exports.isHex = common_isHex;
exports.isOct = common_isOct;
exports.isBin = common_isBin;
exports.isAlpha = common_isAlpha;
exports.isLetter = common_isLetter;
exports.isIdentStart = common_isIdentStart;
exports.isIdentMiddle = common_isIdentMiddle;
exports.isWord = common_isWord;
exports.upperFirst = common_upperFirst;
